package io.gitee.zhangbinhub.acp.cloud.resource.server

import cn.dev33.satoken.SaManager
import cn.dev33.satoken.context.model.SaRequest
import cn.dev33.satoken.context.model.SaResponse
import cn.dev33.satoken.context.model.SaStorage
import cn.dev33.satoken.dao.SaTokenDao
import cn.dev33.satoken.dao.SaTokenDaoForRedisson
import cn.dev33.satoken.`fun`.strategy.SaCorsHandleFunction
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts
import cn.dev33.satoken.serializer.impl.SaSerializerTemplateForJdkUseBase64
import io.gitee.zhangbinhub.acp.boot.AcpExceptionAutoConfiguration
import io.gitee.zhangbinhub.acp.boot.conf.AcpCorsConfiguration
import io.gitee.zhangbinhub.acp.cloud.AcpCloudComponentAutoConfiguration
import io.gitee.zhangbinhub.acp.cloud.AcpCloudPropertiesAutoConfiguration
import io.gitee.zhangbinhub.acp.cloud.constant.RestPrefix
import io.gitee.zhangbinhub.acp.cloud.resource.server.component.AuthPermission
import io.gitee.zhangbinhub.acp.cloud.resource.server.conf.AcpCloudResourceServerConfiguration
import io.gitee.zhangbinhub.acp.cloud.resource.server.exceptions.AcpCloudResourceServerWebExceptionHandler
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import org.redisson.api.RedissonClient
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.web.ServerProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.http.HttpHeaders

/**
 * @since JDK 17
 */
@AutoConfiguration(
    after = [AcpCloudPropertiesAutoConfiguration::class, AcpCloudComponentAutoConfiguration::class, AcpExceptionAutoConfiguration::class]
)
@EnableConfigurationProperties(AcpCloudResourceServerConfiguration::class)
class AcpCloudResourceServerComponentAutoConfiguration(
    private val acpCloudResourceServerConfiguration: AcpCloudResourceServerConfiguration,
    serverProperties: ServerProperties
) {
    init {
        SaManager.setSaSerializerTemplate(SaSerializerTemplateForJdkUseBase64())
    }

    private val contextPath: String =
        if (CommonTools.isNullStr(serverProperties.servlet.contextPath)) "" else serverProperties.servlet.contextPath

    @Bean
    fun acpCloudResourceServerWebExceptionHandler() = AcpCloudResourceServerWebExceptionHandler()

    @Bean
    fun authPermission(): AuthPermission = AuthPermission()

    @Bean
    fun saTokenDao(redissonClient: RedissonClient): SaTokenDao {
        return SaTokenDaoForRedisson(redissonClient)
    }

    /**
     * 放行安全检查的开放Path
     */
    fun permitAllPath() = mutableListOf<String>().apply {
        this.add("$contextPath/error")
        this.add("$contextPath/favicon.ico")
        this.add("$contextPath/swagger/**")
        this.add("$contextPath/swagger-resources")
        this.add("$contextPath/v3/api-docs/**")
        this.add("$contextPath/doc.html")
        this.add("$contextPath/webjars/**")
        acpCloudResourceServerConfiguration.permitAllPath.forEach { path -> this.add(contextPath + path) }
        this.add(contextPath + RestPrefix.OPEN + "/**")
    }

    fun permitOauthApi() = mutableListOf<String>().apply {
        this.add("$contextPath${SaOAuth2Consts.Api.authorize}")
        this.add("$contextPath${SaOAuth2Consts.Api.token}")
        this.add("$contextPath${SaOAuth2Consts.Api.refresh}")
        this.add("$contextPath${SaOAuth2Consts.Api.revoke}")
        this.add("$contextPath${SaOAuth2Consts.Api.client_token}")
        this.add("$contextPath${SaOAuth2Consts.Api.doLogin}")
        this.add("$contextPath${SaOAuth2Consts.Api.doConfirm}")
    }

    fun saCorsHandleFunction(acpCorsConfiguration: AcpCorsConfiguration): SaCorsHandleFunction =
        SaCorsHandleFunction { request: SaRequest, response: SaResponse, storage: SaStorage? ->
            val origin = request.getHeader(HttpHeaders.ORIGIN)
            if (CommonTools.isNullStr(origin)) {
                return@SaCorsHandleFunction
            }
            response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, acpCorsConfiguration.maxAge.toString())
            if (acpCorsConfiguration.allowCredentials) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")
            }
            if (acpCorsConfiguration.allowedMethods.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
                    acpCorsConfiguration.allowedMethods.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.allowedHeaders.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
                    acpCorsConfiguration.allowedHeaders.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.exposedHeaders.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
                    acpCorsConfiguration.exposedHeaders.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.allowedOrigins.contains("*") || acpCorsConfiguration.allowedOrigins.contains(
                    origin
                )
            ) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin)
            }
        }
}
